import Mock from 'mockjs';

/**
 * 发布者
 * @param {*} data 双向数据
 */
function Observer(data) {
	if (typeof data !== 'object' || !data) return;
	Object.keys(data).forEach(key => {
		let value = data[key];
		Observer(value); //递归嵌套对象
		let dep = new Dep();
		Object.defineProperty(data, key, {
			configurable: true, //是否允许delete
			enumerable: true, //是否允许枚举
			get() {
				if (Dep.target) {
					// 在消息管理器原型的target不为空的时候，把这个订阅者加进队列中
					dep.addSub(Dep.target);
				}
				return value;
			},
			set(val) {
				if (val === value) return;
				value = val;
				dep.notify(); //当属性的值发生变化时，通知该消息管理器实例下的全部订阅者
			},
		});
	});
}

/**
 * 订阅者
 * @param {*} vm this
 * @param {*} key 需要监听的key
 * @param {*} callback 值更新后触发的回调
 */
function Watcher(vm, key, callback) {
	this.vm = vm;
	this.key = key;
	this.callback = callback;
	this.value = this.get();
}
Watcher.prototype = {
	// 获取value，触发getter
	get() {
		Dep.target = this; //设置消息管理器原型的target成当前上下文,通过getter的入队判断
		let value = this.vm.data[this.key]; //读取属性值，触发getter（入队）
		Dep.target = null; // 清空自己
		return value; //设置value
	},
	//更新时触发
	update() {
		// todo 虚拟dom检查
		this.run();
	},
	//触发回调
	run() {
		let newVal = this.vm.data[this.key];
		let oldVal = this.value;
		this.callback.call(this.vm, newVal, oldVal); //绑定上下文，传递更新前后的值
		this.value = newVal;
	},
};

// 消息管理器
function Dep() {
	this.subs = []; //订阅者队列
}
Dep.prototype = {
	/**
	 * 订阅者入队
	 * @param {*} sub Watcher实例
	 */
	addSub(sub) {
		this.subs.push(sub); //添加一个订阅者
	},
	/**
	 * 遍历发送通知
	 */
	notify() {
		this.subs.forEach(sub => {
			sub.update(); //通知每一个订阅者触发回调
		});
	},
};
Dep.target = null; // 临时对象，用于缓存Watcher实例

/**
 * 模板解析
 * @param {*} el dom
 * @param {*} vm this
 */
function Compile(el, vm) {
	this.el = document.querySelector(el);
	this.vm = vm;
	this.fragment = null;
	this.init();
}
Compile.prototype = {
	init() {
		if (!this.el) throw 'Dom is undefined';
		this.fragment = this.transformToFragment(this.el); // 转换成dom片段
		this.compileFragment(this.fragment); // 编译模板
		this.compileDirective(this.fragment); // 编译指令
		this.el.appendChild(this.fragment); // 重绘编译结果
	},
	transformToFragment(dom) {
		let fragment = document.createDocumentFragment();
		let child = dom.firstChild;
		while (child) {
			fragment.appendChild(child);
			child = dom.firstChild;
		}
		return fragment;
	},
	compileFragment(fragment) {
		let regexp = /\{\{(.+)\}\}/;
		fragment.childNodes.forEach(node => {
			let text = node.textContent;
			if (node.childNodes && node.childNodes.length) {
				this.compileFragment(node);
			}
			if (node.nodeType === 3 && regexp.test(text)) {
				let key = regexp.exec(text)[1].replace(/(^\s*)|(\s*$)/g, '');
				node.textContent = this.vm[key];
				new Watcher(this.vm, key, (newVal, oldVal) => {
					node.textContent = newVal;
				});
			}
		});
	},
	compileDirective(fragment) {
		fragment.childNodes.forEach(node => {
			let nodeAttrs = node.attributes || {}; // NamedNodeMap对象，一个包含节点属性的类数组对象
			if (node.childNodes && node.childNodes.length) {
				this.compileDirective(node);
			}
			Array.prototype.forEach.call(nodeAttrs, attr => {
				if (/^v-model$/i.test(attr.name)) {
					node.value = this.vm[attr.value];
					// 添加订阅者，当外部改变时将被通知到
					new Watcher(this.vm, attr.value, (newVal, oldVal) => {
						node.value = newVal;
					});
					// 注册输入事件，直接更新vue实例上的值（当内部改变时将通知外部）
					node.addEventListener('input', e => {
						if (this.vm[attr.value] === e.target.value) return;
						this.vm[attr.value] = e.target.value;
					});
					node.removeAttribute(attr.name);
				}
				if (/^v-on:.+$/i.test(attr.name)) {
					let eventType = attr.name.replace(/^v-on:/i, '').toLowerCase();
					node.addEventListener(eventType, e => {
						this.vm[attr.value]();
					});
					node.removeAttribute(attr.name);
				}
			});
		});
	},
};

/**
 * 模拟实现vue框架
 * @param {*} options 配置
 */
function FrameVue(options) {
	this.vm = this;
	this.data = options.data;
	this.routers = options.routers || [];
	this.proxyData(); // 代理data
	this.proxyMethods(options.methods); // 代理methods
	Observer(this.data); // 设置getter setter监听
	new Compile(options.el, this.vm);
	if (this.routers.length) this.routerInit(); // 初始化路由
	if (options.mounted instanceof Function) options.mounted.call(this); // 触发mounted钩子
	return this;
}
FrameVue.prototype = {
	// 代理data，映射到实例上
	proxyData() {
		Object.keys(this.data).forEach(key => {
			Object.defineProperty(this, key, {
				configurable: true,
				enumerable: false, //因为是代理的属性，所以没必要枚举
				get() {
					return this.data[key]; // 读取时，从data中取出相关映射
				},
				set(val) {
					this.data[key] = val; // 设置上，设置到data的相应映射上
				},
			});
		});
	},
	proxyMethods(methods) {
		Object.keys(methods).forEach(key => {
			Object.defineProperty(this, key, {
				configurable: false,
				enumerable: true,
				get() {
					return methods[key];
				},
			});
		});
	},
	routerInit() {
		window.addEventListener('hashchange', e => {
			this.hashChange(e);
		});
		if (window.location.hash) {
			this.hashChange({ oldURL: '', newURL: window.location.href });
		} else {
			window.location.hash = this.routers[0].path;
		}
	},
	hashChange(e) {
		let oldHash = e.oldURL.split('#')[1];
		let newHash = e.newURL.split('#')[1];
		this.routers.forEach(item => {
			if (item.path === newHash) {
				document.querySelector('#router-view').textContent = item.component;
			}
		});
	},
};

// 实例化
let frameVue = new FrameVue({
	el: '#app',
	data: { msg: 'hello world!', count: 0 },
	routers: [
		{
			path: 'main',
			component: 'MAIN视图',
		},
		{
			path: 'user',
			component: 'USER视图',
		},
	],
	mounted() {
		// this.showNotify();
	},
	methods: {
		showNotify() {
			if (window.Notification && Notification.permission !== 'denied') {
				Notification.requestPermission(status => {
					new Notification('当前时间', { body: new Date() });
				});
			}
		},
		addCount() {
			this.count++;
		},
		randomMsg() {
			this.msg = Mock.Random.city();
		},
	},
});

// 注册原生点击事件
document.getElementById('btn-count').onclick = e => {
	frameVue.count++; //通过代理可以直接取到data下的属性
};
